﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/********************************************************
 * ADO.NET 2.0 Data Provider for SQLite Version 3.X
 * Written by Robert Simpson (robert@blackcastlesoft.com)
 * 
 ********************************************************/


using System;
using System.Data;
using System.Data.Common;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using EffiProz.Core;
using EffiProz.Core.Results;

namespace System.Data.EffiProz
{
    public sealed class EfzTransaction : DbTransaction
    {
        /// <summary>
        /// The connection to which this transaction is bound
        /// </summary>
        internal EfzConnection _cnn;
      
        private IsolationLevel _isolationLevel;
        private bool _readOnly;     
      
        public bool IsCompleted { get; set; }

        internal EfzTransaction(EfzConnection connection)  
        {
            BeginTransaction(connection, connection.IsolationLevel, connection.ReadOnly);
        }

        /// <summary>
        /// Constructs the transaction object, binding it to the supplied connection
        /// </summary>
        /// <param name="connection">The connection to open a transaction on</param>

        internal EfzTransaction(EfzConnection connection, IsolationLevel isolationLevel)
        {           
             BeginTransaction(connection, isolationLevel, connection.ReadOnly);
           
        }

        internal EfzTransaction(EfzConnection connection, IsolationLevel isolationLevel, bool readOnly)
        {
            BeginTransaction(connection, isolationLevel, readOnly);
        }

        /// <summary>
        /// Constructs the transaction object, binding it to the supplied connection
        /// </summary>
        /// <param name="connection">The connection to open a transaction on</param>

        internal void BeginTransaction(EfzConnection connection, IsolationLevel isolationLevel,
            bool readOnly)
        {
            _cnn = connection;            
            _isolationLevel = isolationLevel;
            _readOnly = readOnly;

            SetTransactionCharacteristics();

            IsValid(true);
        }

        private void SetTransactionCharacteristics()
        {
            if (_cnn.ReadOnly == ReadOnly && _cnn.IsolationLevel == IsolationLevel)
                return;

            _cnn.InnerConnection.SessionProxy.rollback(false);

            StringBuilder sb = new StringBuilder();
            sb.Append("SET TRANSACTION ");

            if (ReadOnly)
                sb.Append("READ ONLY ");
            else
                sb.Append("READ WRITE ");

            sb.Append(" ;");
            string sql = sb.ToString();
            _cnn.InnerConnection.ExecuteDirect(sql);

            sb = new StringBuilder();
            sb.Append("SET TRANSACTION ISOLATION LEVEL ");

            switch (IsolationLevel)
            {
                case IsolationLevel.ReadUncommitted:
                    sb.Append("READ UNCOMMITTED ");
                    break;
                case IsolationLevel.RepeatableRead:
                    sb.Append("REPEATABLE READ ");
                    break;
                case IsolationLevel.Serializable:
                    sb.Append("SERIALIZABLE  ");
                    break;
                case IsolationLevel.ReadCommitted:
                default:
                    sb.Append("READ COMMITTED ");
                    break;
            }


            sb.Append(" ;");
            sql = sb.ToString();           
            _cnn.InnerConnection.ExecuteDirect(sql);
        }

        private void GetTransactionCharacteristics()
        {

        }

        public bool PrepareCommit()
        {
            if(!IsValid(false))
                return false;         

            try
            {
                _cnn.InnerConnection.SessionProxy.prepareCommit();                              
            }
            catch (CoreException )
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Commits the current transaction.
        /// </summary>
        public override void Commit()
        {
            IsValid(true);

            EfzConnection cnn = Interlocked.Exchange(ref _cnn, null);           

            try
            {
                cnn.InnerConnection.SessionProxy.commit(true);

            }
            catch (CoreException e)
            {
                EfzException.ThrowError(e);
            }
             
            IsCompleted = true;
        }

        public void ReleaseSavePoint(string savePoint)
        {
            try
            {

                _cnn.InnerConnection.SessionProxy.releaseSavepoint(savePoint);

            }
            catch (CoreException e)
            {
                EfzException.ThrowError(e);
            }
        }

        /// <summary>
        /// Returns the underlying connection to which this transaction applies.
        /// </summary>
        public new EfzConnection Connection
        {
            get{ return _cnn;}
        }

        public override IsolationLevel IsolationLevel
        {
            get { return _isolationLevel; }           
        }

        public bool ReadOnly
        {
            get { return _readOnly; }       
        }

        /// <summary>
        /// Forwards to the local Connection property
        /// </summary>
        protected override DbConnection DbConnection
        {
            get { return Connection; }
        }
              

        /// <summary>
        /// Disposes the transaction.  If it is currently active, any changes are rolled back.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            lock (this)
            {
                if (IsValid(false))
                {
                    IssueRollback();
                }
                base.Dispose(disposing);
            }
        }

        public void CreateSavePoint(string savePoint)
        {
            if (String.IsNullOrEmpty(savePoint))
                throw new ArgumentException("Required argumet missing : SavePoint");

            IsValid(true);

            try
            {
                _cnn.InnerConnection.SessionProxy.savepoint(savePoint);

            }
            catch (CoreException e)
            {
                EfzException.ThrowError(e);
            }
        }    
   
        /// <summary>
        /// Rolls back the active transaction.
        /// </summary>
        public override void Rollback()
        {
                IsValid(true);
                IssueRollback();
                IsCompleted = true;           
        }

        /// <summary>
        /// Rolls back the active transaction.
        /// </summary>
        public void RollbackToSavePoint(string savePoint)
        {
            IsValid(true);
            try
            {
                _cnn.InnerConnection.SessionProxy.rollbackToSavepoint(savePoint);

            }
            catch (CoreException e)
            {
                EfzException.ThrowError(e);
            }
        }

        internal void IssueRollback()
        {
            EfzConnection cnn = Interlocked.Exchange(ref _cnn, null);

            if (cnn != null)
            {
                try
                {
                    cnn.InnerConnection.SessionProxy.rollback(true);
                }
                catch (CoreException e)
                {
                    EfzException.ThrowError(e);
                }

            }
        }

        internal bool IsValid(bool throwError)
        {
            if (_cnn == null)
            {
                if (throwError == true) throw new ArgumentNullException("No connection associated with this transaction");
                else return false;
            }
          
            if (_cnn.State != ConnectionState.Open)
            {
                if (throwError == true) throw new EfzException("Connection was closed");
                else return false;
            }

            if (IsCompleted ||  _cnn.InnerConnection.SessionProxy.isAutoCommit() )
            {               
                if (throwError == true) throw new EfzException("No transaction is active on this connection");
                else return false;
            }

            return true;
        }
    }
}
